using System;
using Rubikev.Common.DependencyInjection;
using StructureMap;
using StructureMap.Configuration.DSL;
using StructureMap.Configuration.DSL.Expressions;

namespace Rubikev.StructureMap
{
   /// <summary>
   /// Registers implementations using StructureMap
   /// </summary>
   public class TypeRegister : ITypeRegister
   {
      /// <summary>
      /// Registers an implementation type for a contract type
      /// </summary>
      /// <param name="scope">The optional scope for the type. Defaults to singleton</param>
      /// <typeparam name="TContract">The type of the contract that the implementation implements</typeparam>
      /// <typeparam name="TImplementation">The type of the implementation that will be used when the <typeparamref name="TContract"/> is requested</typeparam>
      public void RegisterType<TContract, TImplementation>(LifetimeScope scope = LifetimeScope.Singleton) where TImplementation : TContract
      {
         ObjectFactory.Configure(x => CreatePluginFamilyExpression<TContract>(x, scope).Use<TImplementation>());
      }

      /// <summary>
      /// Registers a creation method for a contract type
      /// </summary>
      /// <param name="creationMethod">The method used to create an instance that implements the contract</param>
      /// <param name="scope">The optional scope for the type. Defaults to singleton</param>
      /// <typeparam name="TContract">The type of the contract that the <paramref name="creationMethod"/> returns</typeparam>
      public void RegisterType<TContract>(Func<TContract> creationMethod, LifetimeScope scope = LifetimeScope.Singleton)
      {
         ObjectFactory.Configure(x => CreatePluginFamilyExpression<TContract>(x, scope).Use(creationMethod));
      }

      /// <summary>
      /// Registers an implementation type for a contract type. This is provided for open generic support
      /// </summary>
      /// <param name="scope">The optional scope for the type. Defaults to singleton</param>
      /// <param name="contractType">The type of the contract that the implementation implements</param>
      /// <param name="implementationType">The type of the implementation that will be used when the <paramref name="contractType"/> is requested</param>
      public void RegisterType(Type contractType, Type implementationType, LifetimeScope scope = LifetimeScope.Singleton)
      {
         ObjectFactory.Configure(x =>
                                    {
                                       GenericFamilyExpression genericFamilyExpression = x.For(contractType);

                                       /* Annoyingly, GenericFamilyExpression and CreatePluginFamilyExpression don't implement a common interface, so we 
                                        * have to write the scope adaptation code twice
                                        */
                                       switch (scope)
                                       {
                                          case LifetimeScope.Singleton:
                                             genericFamilyExpression = genericFamilyExpression.Singleton();
                                             break;
                                          case LifetimeScope.HttpContext:
                                             genericFamilyExpression = genericFamilyExpression.HttpContextScoped();
                                             break;
                                          case LifetimeScope.Instance:
                                             genericFamilyExpression =
                                                genericFamilyExpression.LifecycleIs(InstanceScope.Unique);
                                             break;
                                          default:
                                             throw new ArgumentException(string.Format("Could not scope object to {0} using StructureMap", scope));
                                       }

                                       genericFamilyExpression.Use(implementationType);
                                    });
      }

      /// <summary>
      /// Applies the <paramref name="scope"/> to the plugin type
      /// </summary>
      /// <typeparam name="TContract">The type of the contract</typeparam>
      /// <param name="x">The StructureMap registry</param>
      /// <param name="scope">The lifetime scope to apply to the plugin</param>
      /// <returns>The plugin with the scope applied</returns>
      private static CreatePluginFamilyExpression<TContract> CreatePluginFamilyExpression<TContract>(Registry x, LifetimeScope scope)
      {
         CreatePluginFamilyExpression<TContract> pluginFamilyExpression = x.For<TContract>();

         switch (scope)
         {
            case LifetimeScope.Singleton:
               pluginFamilyExpression = pluginFamilyExpression.Singleton();
               break;
            case LifetimeScope.HttpContext:
               pluginFamilyExpression = pluginFamilyExpression.HttpContextScoped();
               break;
            case LifetimeScope.Instance:
               pluginFamilyExpression = pluginFamilyExpression.AlwaysUnique();
               break;
            default:
               throw new ArgumentException(string.Format("Could not scope object to {0} using StructureMap", scope), "scope");
         }

         return pluginFamilyExpression;
      }
   }
}